package cn.edu.neu.lab603.cloudeval.experiment

import java.io.{File, FileOutputStream, PrintStream}
import java.text.SimpleDateFormat
import java.util.Date

import cn.edu.neu.lab603.cloudeval.CloudEval
import cn.edu.neu.lab603.cloudeval.entity.model.power.EmpiricalPowerModel
import cn.edu.neu.lab603.cloudeval.entity.model.{ConstantResModel, RandomResModel}
import cn.edu.neu.lab603.cloudeval.entity.{CloudTask, UserCloudTask, PhysicalMachine => Host, VirtualMachine => VM}
import cn.edu.neu.lab603.cloudeval.monitor._
import cn.edu.neu.lab603.cloudeval.strategy.{ReallocationStrategy, SimpleOverloadAllocationStrategy}
import cn.edu.neu.lab603.des.{MetricsLogMonitor, SimMetric, SimMonitor}

import scala.collection.mutable.ListBuffer
import scala.language.implicitConversions

protected trait MonitorsLike {
  protected[this] val monitors = new ListBuffer[SimMonitor[CloudEval]]

  protected def addMonitor[E <: SimMonitor[CloudEval]](monitor: E): E = {
    monitors += monitor
    monitor
  }
}
/** 实验设置
  *
  * Created by yfwz100 on 2016/11/11.
  */
class ExperimentSetup extends MonitorsLike {

  private val prefix = "/Users/yfwz100/tmp/experiments"

  val execDelayMonitor: CloudletExecDelayMetric = addMonitor(new CloudletExecDelayMetric)
  val waitingTimeMetric: CloudletWaitingTimeMetric = addMonitor(new CloudletWaitingTimeMetric)
  val migrationTimes: CounterMetric[VM.MigrationPostCopyEvent] = addMonitor(new CounterMetric(classOf[VM.MigrationPostCopyEvent]))
  val powerModel: EmpiricalPowerModel = new EmpiricalPowerModel
  val powerMetric: PowerMetric = addMonitor(new PowerMetric(powerModel))
  val cloudletSLAMonitor: CloudletSLAMetric = addMonitor(new CloudletSLAMetric)
  val hostBootTimesMetric: CounterMetric[Host.BootEvent] = addMonitor(new CounterMetric(classOf[Host.BootEvent]))
  val hostShutdownTimesMetric: CounterMetric[Host.ShutdownEvent] = addMonitor(new CounterMetric(classOf[Host.ShutdownEvent]))
  val vmBootTimesMetric: CounterMetric[VM.BootEvent] = addMonitor(new CounterMetric(classOf[VM.BootEvent]))
  val vmShutdownTimesMetric: CounterMetric[VM.ShutdownEvent] = addMonitor(new CounterMetric(classOf[VM.ShutdownEvent]))
  val migrationDownTimeMonitor: MigrationDownTimeMonitor = addMonitor(new MigrationDownTimeMonitor)
  val migrationDurationMonitor: MigrationDurationMonitor = addMonitor(new MigrationDurationMonitor)
  val resSaturateMonitor: ResSaturateMetric = addMonitor(new ResSaturateMetric(Map(
    "core" -> (0.0, 1.0),
    "mips" -> (0.3, 0.8),
    "ram" -> (0.0, 1.0),
    "bw" -> (0.0, 1.0)
  )))
  val networkUtilityMonitor: NetworkUtilityMonitor = addMonitor(new NetworkUtilityMonitor)
  val liveTimeMonitor: CloudletLiveTimeMonitor = addMonitor(new CloudletLiveTimeMonitor)
  val utilizationMonitor: HostUtilizationMonitor = addMonitor(new HostUtilizationMonitor)
  val vmMigrationDurationMetric: SimMetric[Long] = SimMetric(migrationDurationMonitor)(_.stats.values.map(_.sum).sum)
  val vmMigrationDownTimeMetric: SimMetric[Long] = SimMetric(migrationDownTimeMonitor)(_.stats.values.map(_.sum).sum)

  val fom: FitnessScoreMetric = new FitnessScoreMetric(
    migrationDurationMonitor,
    migrationDownTimeMonitor,
    cloudletSLAMonitor,
    powerMetric
  )
  val slaCost: SimMetric[Double] = SimMetric { () =>
    val m = 2
    0.6 * cloudletSLAMonitor.getSLAInfo.map { p =>
      val (cloudletNo, (actualTime, planTime)) = p
      val cloudlet = CloudTask.get(cloudletNo)
      val power = 100 * cloudlet.getResSpecMap("core") / 3600000.0
      m * power * planTime * (actualTime.toDouble / planTime - 1)
    }.sum
  }
  val migCost: SimMetric[Double] = SimMetric { () =>
    val m = 2
    val migDownTime = migrationDownTimeMonitor.stats.map { p =>
      val (cloudletNo, downTimes) = p
      val cloudlet = CloudTask.get(cloudletNo)
      val power = 100 * cloudlet.getResSpecMap("core") / 3600000.0
      m * power * downTimes.sum
    }.sum
    val migMigrationTime = migrationDurationMonitor.stats.map { p =>
      val (cloudletNo, durations) = p
      val cloudlet = CloudTask.get(cloudletNo)
      val power = 100 * cloudlet.getResSpecMap("core") / 3600000.0
      m * power * durations.sum
    }.sum
    val mig = 0.8 * migDownTime + 0.2 * migMigrationTime
    0.4 * mig
  }
  val wem: SimMetric[Double] = SimMetric(() => {
    val power = powerMetric.getCurrentValue
    val sla = slaCost.getCurrentValue
    val mig = migCost.getCurrentValue
    (power + (sla + mig)) / 3600000.0
  })

  private implicit def toValue[T](metric: SimMetric[T]): T = metric.getCurrentValue

  class Experiment(val migrationStrategy: ReallocationStrategy) {

    def run(): Unit = {
      val simulation = new CloudEval

      monitors.foreach(simulation.addMonitor)

      val logMonitor = new MetricsLogMonitor(
        s"$prefix/1201-$migrationStrategy-u270-h170.csv",
        Seq(
          // simple statistics
          "hostBootTimes" -> hostBootTimesMetric,
          "hostShutTimes" -> hostShutdownTimesMetric,
          "vmBootTimes" -> vmBootTimesMetric,
          "vmShutTimes" -> vmShutdownTimesMetric,
          // power
          "power" -> powerMetric,
          // performance
          "execDelays" -> execDelayMonitor,
          "waitingTimes" -> waitingTimeMetric,
          "overallSlav" -> cloudletSLAMonitor.overallSLAMetric,
          "averageSlav" -> cloudletSLAMonitor.averageSLAMetric,
          "vmMigrations" -> migrationTimes,
          "vmMigrationDuration" -> vmMigrationDurationMetric,
          "vmMigrationDownTime" -> vmMigrationDownTimeMetric,
          "averageLiveTime" -> liveTimeMonitor.getAverageFrustrationMetric(),
          // balance
          "resSaturate" -> resSaturateMonitor.getLastMetric,
          "vmBalance" -> SimMetric(networkUtilityMonitor)(_.getBalanceLevel(simulation))
        )
      )

      simulation.addMonitor(logMonitor)

      val dc = simulation.createDataCenter(
        new SimpleOverloadAllocationStrategy(2),
        migrationStrategy, {
          (0 until 10).map(_ => new Host(Map(
            "core" -> 1,
            "mips" -> 1000,
            "ram" -> 4096,
            "bw" -> 10240
          ))) ++ (0 until 10).map(_ => new Host(Map(
            "core" -> 2,
            "mips" -> 2000,
            "ram" -> 8192,
            "bw" -> 10240
          ))) ++ (0 until 150).map(_ => new Host(Map(
            "core" -> 4,
            "mips" -> 4000,
            "ram" -> 32768,
            "bw" -> 10240
          )))
        }.toList
      )

      val user = simulation.createUser({
        val cloudletStart = 15
        val cloudletLen = 60 * 60
        (0 until 200).map { _ =>
          new UserCloudTask(// 类型 1
          cloudletStart, // 开始时间
            (cloudletLen * (0.5 + math.random)).toInt, // 长度
          Map(
            "core" -> ConstantResModel(1),
            "mips" -> RandomResModel(600, 900, 1000),
            "ram" -> RandomResModel(256, 512, 512),
            "bw" -> ConstantResModel(0, 512)
          ),
          dc
        )
        } ++ (0 until 90).map { _ =>
          new UserCloudTask(// 类型 2
          cloudletStart, // 开始时间
            (cloudletLen * (0.5 + math.random)).toInt, // 长度
          Map(
            "core" -> ConstantResModel(1),
            "mips" -> RandomResModel(300, 800, 1000),
            "ram" -> RandomResModel(51, 102, 102),
            "bw" -> ConstantResModel(0, 512)
          ),
          dc
        )
        } ++ (0 until 100).map { _ =>
          new UserCloudTask(// 类型 3
          cloudletStart, // 开始时间
            (cloudletLen * (0.5 + math.random)).toInt, // 长度
          Map(
            "core" -> ConstantResModel(1),
            "mips" -> RandomResModel(500, 950, 1000),
            "ram" -> RandomResModel(512, 2048, 2048),
            "bw" -> ConstantResModel(0, 512)
          ),
          dc
        )
        } ++ (0 until 100).map { _ =>
          new UserCloudTask(// 类型 4
          cloudletStart, // 开始时间
            (cloudletLen * (0.5 + math.random)).toInt, // 长度
          Map(
            "core" -> ConstantResModel(2),
            "mips" -> RandomResModel(1000, 1900, 2000),
            "ram" -> RandomResModel(512, 1024, 1024),
            "bw" -> ConstantResModel(0, 512)
          ),
          dc
        )
        } ++ (0 until 50).map { _ =>
          new UserCloudTask(// 类型 5
          cloudletStart, // 开始时间
            (cloudletLen * (0.5 + math.random)).toInt, // 长度
          Map(
            "core" -> ConstantResModel(2),
            "mips" -> RandomResModel(1000, 1500, 2000),
            "ram" -> RandomResModel(1024, 2048, 2048),
            "bw" -> ConstantResModel(0, 512)
          ),
          dc
        )
        } ++ (0 until 10).map { _ =>
          new UserCloudTask(// 类型 6
          cloudletStart, // 开始时间
            (cloudletLen * (0.5 + math.random)).toInt, // 长度
          Map(
            "core" -> ConstantResModel(3),
            "mips" -> RandomResModel(1500, 2000, 2500),
            "ram" -> RandomResModel(2048, 4096, 4096),
            "bw" -> ConstantResModel(0, 512)
          ),
          dc
        )
        }
      }.toList)

      simulation.run(5 * 60 * 60)

      println()

      val finishedCloudlets = user.getCloudlets.count(_.getStatus == CloudTask.Status.STOPPED)
      val readyCloudlets = user.getCloudlets.count(_.getStatus == CloudTask.Status.READY)
      val runningCloudlets = user.getCloudlets.count(_.getStatus == CloudTask.Status.RUNNING)
      //      user.getCloudlets.filter(_.getStatus == Cloudlet.Status.RUNNING).foreach(println)

      println(s"Experiment Name: $migrationStrategy")
      println(s"Finished cloudlets: $finishedCloudlets")
      println(s"Ready cloudlets: $readyCloudlets")
      println(s"Running cloudlets: $runningCloudlets")

      println(s"Number of hosts boot: $hostBootTimesMetric")
      println(s"Number of hosts shutdown: $hostShutdownTimesMetric")
      println(s"Number of VM boot: $vmBootTimesMetric")
      println(s"Number of VM shutdown: $vmShutdownTimesMetric")

      println(s"Number of migrations: $migrationTimes")
      println(s"Number of migrated VMs: ${migrationDownTimeMonitor.stats.size}")
      println(s"Duration of migration: $vmMigrationDurationMetric")
      println(s"Down time of migration: $vmMigrationDownTimeMetric")

      println(s"Overall frustrations: ${liveTimeMonitor.getOverallFrustrationMetric()}")
      println(s"Average frustrations: ${liveTimeMonitor.getAverageFrustrationMetric()}")
      println(s"Saturation of data center: ${resSaturateMonitor.getAverageMetric}")

      print(s"Cloudlet execution delays: $execDelayMonitor ")
      if (execDelayMonitor.stats.nonEmpty)
        println(f"(Average: ${execDelayMonitor / execDelayMonitor.stats.size.toDouble}%2.3f)")
      else
        println()

      print(s"Waiting Time: $waitingTimeMetric")
      if (waitingTimeMetric.stats.nonEmpty)
        println(f"(Average: ${waitingTimeMetric / waitingTimeMetric.stats.size}%2.3f)")
      else
        println()

      println(f"Overall percent of SLA violations: ${cloudletSLAMonitor.overallSLAMetric * 100}%2.3f%%")
      println(f"Average percent of SLA violations: ${cloudletSLAMonitor.averageSLAMetric * 100}%2.3f%%")
      println(f"Power Consumption: ${powerMetric / 3600000.0}%4.3fkWh")

      println()

      println(f"Fitness of Migration: ${fom.getCurrentValue}%4.3f")
      println(f"Comprehensive Score: ${wem.getCurrentValue}%4.3f")

      if (!new File(s"$prefix/1205-random.csv").isFile) {
        val out = new PrintStream(new FileOutputStream(s"$prefix/1205-random.csv", true))
        out.println(Seq(
          "date", "name", "clocks", "finished", "running", "migTimes", "migVMs", "migDur", "migDown",
          "liveTime", "resSat", "allSla", "avgSla", "power", "slaCost", "migCost", "fom", "wem"
        ).mkString(","))
        out.close()
      }

      val sdf = new SimpleDateFormat("hh:mm")
      val out = new PrintStream(new FileOutputStream(s"$prefix/1201-random.csv", true))
      out.println(Seq(
        sdf.format(new Date()),
        migrationStrategy.toString,
        simulation.getCurrentClock,
        finishedCloudlets,
        runningCloudlets,
        migrationTimes,
        migrationDownTimeMonitor.stats.size, // 迁移的虚拟机数量
        vmMigrationDurationMetric.getCurrentValue,
        vmMigrationDownTimeMetric.getCurrentValue,
        liveTimeMonitor.getAverageFrustrationMetric().getCurrentValue, // 连续使用指标
        resSaturateMonitor.getAverageMetric.getCurrentValue, // 平衡度
        f"${cloudletSLAMonitor.overallSLAMetric * 100}%2.3f%%",
        f"${cloudletSLAMonitor.averageSLAMetric * 100}%2.3f%%",
        f"${powerMetric / 3600000.0}%4.3f", // 能耗
        slaCost.getCurrentValue, // 服务质量
        migCost.getCurrentValue, // 迁移代价
        f"${fom.getCurrentValue}%4.3f",
        f"${wem.getCurrentValue}%4.3f"
      ).mkString(","))
      out.close()
    }

  }

}

/** 实验设置 */
object ExperimentSetup {

  def setMigrationStrategy(getMigrationStrategy: ExperimentSetup => ReallocationStrategy): ExperimentSetup#Experiment = {
    val setup = new ExperimentSetup
    new setup.Experiment(getMigrationStrategy(setup))
  }
}